package tdengine

import (
	"database/sql"
	"errors"
	"fmt"
	"gitee.com/captials-team/ubdframe/src/common"
	"gitee.com/captials-team/ubdframe/src/domain/configstc"
	"gitee.com/captials-team/ubdframe/src/domain/dto"
	"gitee.com/captials-team/ubdframe/src/domain/dto/paginate"
	"gitee.com/captials-team/ubdframe/src/domain/models"
	"gitee.com/captials-team/ubdframe/src/infrastructure/dao"
	_ "github.com/taosdata/driver-go/v3/taosRestful"
	"gorm.io/gorm/schema"
	"strings"
	"time"
)

func NewIotStoreDao(conf configstc.DBConfig) *IotStoreDao {
	//dsn:="root:taosdata@http(192.168.149.128:6041)/db_test_1"
	dsn := fmt.Sprintf("%s:%s@http(%s:%s)/%s", conf.DbUser, conf.DbPassword, conf.DbHost, conf.DbPort, conf.DbName)
	taos, err := sql.Open("taosRestful", dsn)
	common.ErrPanic(err)

	return &IotStoreDao{
		db: taos,
		naming: schema.NamingStrategy{
			//定义结构体的TableName方法里无法使用传值进行动态表明设置
			//这里可以做统一的表名前缀
			TablePrefix:   conf.TablePrefix,
			SingularTable: true,
			NameReplacer:  nil,
			NoLowerCase:   true,
			//IdentifierMaxLength: 0,
		},
		conf: conf,
	}
}

type IotStoreDao struct {
	db     *sql.DB
	conf   configstc.DBConfig
	naming schema.Namer

	dao.SplitTableDaoHelp
}

func (dao *IotStoreDao) FetchPropertyBySQL(sql string) ([]*models.MetricData, error) {
	return nil, fmt.Errorf("not support")
}

func (dao *IotStoreDao) StatAlarmCount() ([]*models.MetricData, error) {
	return []*models.MetricData{}, nil
}

// 属性
func (dao *IotStoreDao) Add(add *models.MetricData) (int64, error) {
	return dao.BatchAdd(add)
}

func (dao *IotStoreDao) BatchAdd(adds ...*models.MetricData) (int64, error) {
	var s string
	if len(adds) == 0 {
		return 0, nil
	}

	for _, v := range adds {
		if s == "" {
			s = "INSERT INTO"
		}
		s += fmt.Sprintf(" %s USING %s TAGS('%d') VALUES(NOW,'%s')",
			dao.SplitTable(v.MetricId, ""), v.TableName(),
			v.MetricId,
			v.Value,
		)
	}

	affected, err := dao.db.Exec(s)
	if err != nil {
		return 0, err
	}
	return affected.RowsAffected()
}

func (dao *IotStoreDao) QueryLastPropertyValue(metricId int64) (string, int64, error) {
	data, err := dao.QueryLastPropertyData(metricId)
	if err != nil {
		return "", 0, err
	}
	if data == nil {
		return "", 0, nil
	}
	return data.Value, data.Timestamp, nil
}

func (dao *IotStoreDao) QueryLastPropertyData(metricId int64) (*models.MetricData, error) {
	//默认 select * 返回不带tags标签列，需手动加上
	str := fmt.Sprintf("SELECT ts,val,metric FROM %s order by ts asc limit 100", dao.SplitTable(metricId, ""))
	rows, err := dao.db.Query(str)
	if err != nil {
		return nil, err
	}

	defer rows.Close()
	for rows.Next() {
		var data models.MetricData
		var t time.Time
		err := rows.Scan(&t, &data.Value, &data.MetricId)
		if errors.Is(err, sql.ErrNoRows) {
			break
		}
		if err != nil {
			return nil, err
		}
		data.CreatedAt = &t
		data.Timestamp = t.UnixMilli()

		return &data, nil
	}
	return nil, nil
}

func (dao *IotStoreDao) Search(search *dto.SearchMetricDataDto, pa *paginate.Pager) ([]*models.MetricData, *paginate.Pager, error) {
	if search == nil {
		search = &dto.SearchMetricDataDto{}
	}
	if pa == nil {
		pa = &paginate.Pager{}
	}
	pa.Correct()

	var where []string
	if search.MetricId > 0 {
		where = append(where, fmt.Sprintf("metric='%d'", search.MetricId))
	}
	if search.StartTime > 0 {
		where = append(where, fmt.Sprintf("ts>=%d", search.StartTime))
	}
	if search.EndTime > 0 {
		where = append(where, fmt.Sprintf("ts<=%d", search.EndTime))
	}
	whereStr := ""
	if len(where) > 0 {
		whereStr = fmt.Sprintf("WHERE %s", strings.Join(where, " AND "))
	}
	var table = new(models.MetricData).TableName()
	var list []*models.MetricData
	//默认 select * 返回不带tags标签列，需手动加上
	str := fmt.Sprintf("SELECT count(*) FROM %s %s", table, whereStr)
	err := dao.db.QueryRow(str).Scan(&pa.Total)
	if errors.Is(err, sql.ErrNoRows) {
		return list, pa, nil
	}
	if err != nil {
		return list, pa, err
	}

	str = fmt.Sprintf("SELECT ts,val,metric FROM %s %s ORDER BY ts DESC LIMIT %d OFFSET %d ",
		table, whereStr,
		pa.Size,
		pa.Offset(),
	)
	rows, err := dao.db.Query(str)
	if err != nil {
		return list, pa, err
	}

	defer rows.Close()
	for rows.Next() {
		var data models.MetricData
		var t time.Time
		err := rows.Scan(&t, &data.Value, &data.MetricId)
		if errors.Is(err, sql.ErrNoRows) {
			break
		}
		if err != nil {
			return list, pa, err
		}

		data.CreatedAt = &t
		data.Timestamp = t.UnixMilli()

		list = append(list, &data)
	}
	return list, pa, nil
}

func (dao *IotStoreDao) Migrate() (int64, error) {
	//创建数据库
	_, err := dao.db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s PRECISION 'ms' KEEP 365 DURATION 10 BUFFER 16 WAL_LEVEL 1;", dao.conf.DbName))
	if err != nil {
		return 0, err
	}

	//属性（超级表）
	_, err = dao.db.Exec(fmt.Sprintf("CREATE TABLE  IF NOT EXISTS %s(ts TIMESTAMP,val VARCHAR(512)) TAGS (metric VARCHAR(16))",
		new(models.MetricData).TableName(),
	))
	if err != nil {
		return 0, err
	}

	return 0, nil
}

func (dao *IotStoreDao) tableName(d schema.TablerWithNamer) string {
	return d.TableName(dao.naming)
}
